

#include "unit.h"
#include "gamemanager.h"
#include "gamedatamanager.h"
#include "CCSkeletalSprite.h"
#include "bullet.h"
#include "log.h"
#include "messagequeue.h"
#include "army.h"
#include "gamenodecontainer.h"
#include "strutil.h"
#include "gamescene.h"

#include <cmath>

void myfclamp(float &v, float a, float b) {
	if (v < a) v = a;
	if (v > b) v = b;
}

void myiclamp(float &v, float a, float b) {
	if (v < a) v = a;
	if (v > b) v = b;
}

static const int arr_anim_loop[] = {
		1, //birth,
		1, //idle,
		-1, //walk,
		-1, //aim,
		1, //attack,
		1, //reload,
		-1, //walk_aim,
		1, //walk_attack,
		1, //walk_reload,
		1, //dying,
		1, //died,
		1, //count
};

static const bool arr_state_stop_assigning[] = {
		true, //birth,
		true, //idle,
		false, //walk,
		true, //aim,
		true, //attack,
		true, //reload,
		false, //walk_aim,
		false, //walk_attack,
		false, //walk_reload,
		true, //dying,
		true, //died,
		true, //count
};

int Unit::ID_SEED = 1;

Unit::Unit():
id(0)
, creature(nullptr)
, weapon(nullptr)
, armor(nullptr)
, shield(nullptr)
, special(nullptr)
, ourArmy(nullptr)
, theirArmy(nullptr)
, target(nullptr)
, speed(0.0f, 0.0f)
, scale(1.0f)
, fadeOutTime(0.0f)
, fadeInTime(0.0f)
, fadeOutDelay(0.0f)
, fadeInDelay(0.0f)
, gridInfo(0)
, previousPosition(CCPointZero)
, position(CCPointZero)
, targetPosition(CCPointZero)
, _timeElapsed(0.0f)
, _shouldUpdateGridInfo(false)
, isAssigning(false)
, arrivedTargetPosition(false)
, creatureType(CreatureType::one_hand_weapon)
, state(UnitState::birth)
, previousState(UnitState::birth)
, faction(ArmyFaction::left)
, sprite(nullptr) {
	id = ID_SEED++;
}

Unit::~Unit() {
	for (auto bullet : _bullets) {
		gameScene->addBullet(bullet);
	}

	_bullets.clear();

	if(sprite) {
		sprite->removeFromParent();
	}
}

void Unit::init(UnitInfo const &info, float unitScale) {
	scale = unitScale;

	changeEquipment(info);
	sprite->setLoopCount(-1);

	sprintf(buf, "%d", id);
	_labelDebugInfo = CCLabelTTF::create(buf, "", 24);
	_labelDebugInfo->setColor(ccc3(255, 0, 0));
	_labelDebugInfo->setPosition(sprite->getBonePosition("skeleton_body"));
	sprite->addChild(_labelDebugInfo);

#if(CC_TARGET_PLATFORM != CC_PLATFORM_IOS)
	_labelDebugInfo->setVisible(false);
#endif
	transformState(UnitState::idle);

	sprite->registerEventsHandler(std::bind(&Unit::onSpineEvent, this, std::placeholders::_1));

	handleActions(EquipmentActionTriggerType::on_begin);
}

void Unit::update(float ticks) {
	if (state == UnitState::died) {
		return;
	}

	_timeElapsed += ticks;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
	sprintf(buf, "%d\n%d-%d\n%s", id, gridInfo.lineIndex, gridInfo.rowIndex, UNIT_STATE_NAMES[(int) state]);
	_labelDebugInfo->setString(buf);
#endif

	if(fadeOutTime > 0.0f) {
		fadeOutTime -= ticks;
		sprite->setOpacity(static_cast<GLubyte>(255 * (fadeOutTime / fadeOutDelay)));
	} else if(fadeInTime > 0.0f) {
		fadeInTime -= ticks;
		sprite->setOpacity(static_cast<GLubyte>(255 * (1.0f - fadeInTime / fadeInDelay)));

		if(fadeInTime <= 0.0f) {
			sprite->setOpacity(255);
		}
	}

	sprite->update(ticks);

	switch (state) {
		case UnitState::birth: {
			handleStateBirth(ticks);
			break;
		}
		case UnitState::idle: {
			handleStateIdle(ticks);
			break;
		}
		case UnitState::walk: {
			handleStateWalk(ticks);
			break;
		}
		case UnitState::aim: {
			handleStateAim(ticks);
			break;
		}
		case UnitState::attack: {
			handleStateAttack(ticks);
			break;
		}
		case UnitState::reload: {
			handleStateReload(ticks);
			break;
		}
		case UnitState::walk_aim: {
			handleStateWalkAim(ticks);
			break;
		}
		case UnitState::walk_attack: {
			handleStateWalkAttack(ticks);
			break;
		}
		case UnitState::walk_reload: {
			handleStateWalkReload(ticks);
			break;
		}
		case UnitState::dying: {
			handleStateDying(ticks);
			break;
		}
		case UnitState::died: {
			handleActions(EquipmentActionTriggerType::on_death);
			break;
		}
	}

	updateBullets(ticks);

//	sprintf(buf, "%d\n%0.2f\n%s\n%d,%d", id, _timeElapsed, UNIT_STATE_NAMES[(int)state], getGlobalLineIndex(), gridInfo.rowIndex);
//	_labelDebugInfo->setString(buf);
}

void Unit::transformState(UnitState newState) {
	if (state == UnitState::died) {
		return;
	}

	if (state == UnitState::dying && newState != UnitState::died) {
		return;
	}

	if (state == newState) {
		return;
	}

	onExitState(state);
	previousState = state;
	state = newState;
	onEnterState(state);
}

void Unit::onEnterState(UnitState theState) {
	if (state == UnitState::died) {
		return;
	}

	const char *name = UNIT_STATE_NAMES[static_cast<int>(theState)];
	sprite->playAnimation(name, CCSkeletalSprite::tFadeInOut, 0.2f);
	sprite->setLoopCount(arr_anim_loop[(int)state]);

	if(arr_state_stop_assigning[(int)state]) {
		isAssigning = false;
	}

	switch (state) {
		case UnitState::idle:
	        break;
		case UnitState::birth:
	        break;
		case UnitState::attack:
	        break;
		case UnitState::reload:
	        break;
		case UnitState::walk_attack:
	        break;
		case UnitState::walk_reload:
	        break;
		case UnitState::dying:
	        break;
		case UnitState::died:
	        break;
		case UnitState::walk_aim:
	        break;
		case UnitState::aim:
	        break;
		case UnitState::walk:
	        break;
		default:
	        break;
	}

}

void Unit::onExitState(UnitState theState) {

}

void Unit::setPosition(const CCPoint &pos) {
	position = pos;
	updateSpritePosition();
}

void Unit::updateBullets(float ticks) {
	for (auto it = _bullets.begin(); it != _bullets.end();) {
		Bullet *bullet = *it;
		if (!bullet->update(ticks)) {
			onBulletHit(bullet);

			delete bullet;
			it = _bullets.erase(it);
		} else {
			it++;
		}
	}
}

void Unit::updateAttribute() {
	attribute.points = creature->points + weapon->points + armor->points + shield->points + special->points;
	attribute.defense = creature->defense + weapon->defense + armor->defense + shield->defense + special->defense;
	attribute.range = (creature->range > weapon->range ? creature->range : weapon->range);
	attribute.splashCases = weapon->splashCases;

	attribute.projectileType = weapon->projectileType;
	attribute.attackType = weapon->attackType;
	attribute.effectType = weapon->effectType;

	attribute.castingTime = creature->castingTime + weapon->castingTime + special->castingTime + armor->castingTime + shield->castingTime;
	attribute.reloadTime = (creature->reloadTime > weapon->reloadTime ? creature->reloadTime : weapon->reloadTime);

	attribute.slashAttack = creature->slashAttack + weapon->slashAttack + armor->slashAttack + shield->slashAttack + special->slashAttack;
	attribute.bluntAttack = creature->bluntAttack + weapon->bluntAttack + armor->bluntAttack + shield->bluntAttack + special->bluntAttack;
	attribute.piercingAttack = creature->piercingAttack + weapon->piercingAttack + armor->piercingAttack + shield->piercingAttack + special->piercingAttack;
	attribute.explosionAttack = creature->explosionAttack + weapon->explosionAttack + armor->explosionAttack + shield->explosionAttack + special->explosionAttack;
	attribute.fireAttack = creature->fireAttack + weapon->fireAttack + armor->fireAttack + shield->fireAttack + special->fireAttack;
	attribute.iceAttack = creature->iceAttack + weapon->iceAttack + armor->iceAttack + shield->iceAttack + special->iceAttack;
	attribute.poisonAttack = creature->poisonAttack + weapon->poisonAttack + armor->poisonAttack + shield->poisonAttack + special->poisonAttack;
	attribute.thunderAttack = creature->thunderAttack + weapon->thunderAttack + armor->thunderAttack + shield->thunderAttack + special->thunderAttack;
	attribute.darkAttack = creature->darkAttack + weapon->darkAttack + armor->darkAttack + shield->darkAttack + special->darkAttack;
	attribute.holyAttack = creature->holyAttack + weapon->holyAttack + armor->holyAttack + shield->holyAttack + special->holyAttack;

	attribute.slashResist = creature->slashResist + weapon->slashResist + armor->slashResist + shield->slashResist + special->slashResist;
	attribute.bluntResist = creature->bluntResist + weapon->bluntResist + armor->bluntResist + shield->bluntResist + special->bluntResist;
	attribute.piercingResist = creature->piercingResist + weapon->piercingResist + armor->piercingResist + shield->piercingResist + special->piercingResist;
	attribute.explosionResist = creature->explosionResist + weapon->explosionResist + armor->explosionResist + shield->explosionResist + special->explosionResist;
	attribute.fireResist = creature->fireResist + weapon->fireResist + armor->fireResist + shield->fireResist + special->fireResist;
	attribute.iceResist = creature->iceResist + weapon->iceResist + armor->iceResist + shield->iceResist + special->iceResist;
	attribute.poisonResist = creature->poisonResist + weapon->poisonResist + armor->poisonResist + shield->poisonResist + special->poisonResist;
	attribute.thunderResist = creature->thunderResist + weapon->thunderResist + armor->thunderResist + shield->thunderResist + special->thunderResist;
	attribute.darkResist = creature->darkResist + weapon->darkResist + armor->darkResist + shield->darkResist + special->darkResist;
	attribute.holyResist = creature->holyResist + weapon->holyResist + armor->holyResist + shield->holyResist + special->holyResist;

	if (attribute.range == 0) {
		attribute.range = 1;
	}

	if (attribute.castingTime < 2 && weapon->castingTime > 0) {
		attribute.castingTime = 2;
	}

	if (attribute.getTotalAttack(nullptr) == 0) {
		attribute.slashAttack = 1;
	}

	if (attribute.castingTime > 100) {
		_DHLOG("casting time is too large.");
	}
}

void Unit::updateSpritePosition() {
	float factor = (faction == ArmyFaction::left ? -1.0f : 1.0f);

	if (creature->genre == EquipmentGenre::artillery_3case) {
		sprite->setPosition(ccp(position.x + GRID_WIDTH * factor, position.y));
	} else if (creature->genre == EquipmentGenre::artillery_6case) {
		sprite->setPosition(ccp(position.x + GRID_WIDTH * factor, position.y));
	} else if (creature->genre == EquipmentGenre::artillery_9case) {
		sprite->setPosition(ccp(position.x + GRID_WIDTH * factor, position.y));
	} else if (special->genre == EquipmentGenre::special_horse || special->genre == EquipmentGenre::special_boar) {
		sprite->setPosition(ccp(position.x + GRID_WIDTH * 0.5f * factor, position.y));
	} else {
		sprite->setPosition(position);
	}
}

void Unit::addBullet(Bullet *bullet) {
	_bullets.push_back(bullet);

	if (bullet->sprite) {
		sprite->getParent()->addChild(bullet->sprite);
	}
}

void Unit::onBulletHit(Bullet *bullet) {
	int n = bullet->bulletAttribute->getTotalAttack(&attribute);
	attribute.defense -= n;
	if (attribute.defense <= 0) {
		transformState(UnitState::dying);
	} else {
	}

	if(fadeOutTime <= 0.0f && fadeInTime <= 0.0f) {
		fadeOutTime = fadeOutDelay = 0.15f;
		fadeInTime = fadeInDelay = 0.3f;
	}
}

bool Unit::isNormalUnit(const string &name) {
	static const char *arr[] = {
			"ballista",
			"cannon",
			"catapult",
			"giant_flamethrower",
			"plague_cannon",
			"steam_behemoth",
			"steam_chariot",
			"undead_catapult",
	};

	bool ret = true;
	for (int i = 0; i < sizeof(arr) / sizeof(char *); i++) {
		if (strcmp(name.c_str(), arr[i]) == 0) {
			ret = false;
			break;
		}
	}

	return ret;
}

string Unit::getSkeletonFileName(const UnitInfo &info) {
	if (!isNormalUnit(info.creature)) {
		return info.creature.c_str();
	}

	string s = CREATURE_TYPE_NAMES[(int) CreatureType::one_hand_weapon];

	if (GDM->equipmentsAttribute.find(info.weapon) != GDM->equipmentsAttribute.end()) {
		const EquipmentAttribute &attr = GDM->equipmentsAttribute[info.weapon];
		switch (attr.genre) {
			case EquipmentGenre::weapon_1hand:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::one_hand_weapon];
		        break;
			case EquipmentGenre::weapon_2hand:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::two_hand_weapon];
		        break;
			case EquipmentGenre::weapon_staff:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::staff];
		        break;
			case EquipmentGenre::weapon_bow:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::bow];
		        break;
			case EquipmentGenre::weapon_musket:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::musket];
		        break;
			case EquipmentGenre::weapon_pistol:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::pistol];
		        break;
			case EquipmentGenre::weapon_spear:
				s = CREATURE_TYPE_NAMES[(int) CreatureType::spear];
		        break;
			default:
				break;
		}
	}

	return s;
}

void Unit::changeEquipment(const UnitInfo &info) {
	string name = getSkeletonFileName(info);
	if (sprite) {
			sprite->removeFromParent();
	}

	skeletonName = name;

	const EquipmentAttribute& specialAttr = GDM->equipmentsAttribute[info.special];
	if(specialAttr.genre == EquipmentGenre::special_horse) {
		sprintf(buf, "%s_horse", skeletonName.c_str());
		skeletonName = buf;
	} else if(specialAttr.genre == EquipmentGenre::special_boar) {
		sprintf(buf, "%s_boar", skeletonName.c_str());
		skeletonName = buf;
	}

	sprite = CCSkeletalSprite::create(skeletonName.c_str());
	sprite->setAnchorPoint(ccp(0.5f, 0.0f));
	sprite->setScale(scale);
	sprite->playAnimation("idle");

	sprite->applySkin(info.creature.c_str());

	static const char *arrSlots[] = {
			"head",
			"body",
			"shoulder_1",
			"arm_1",
			"hand_1",
			"thigh_1",
			"shank_1",
			"foot_1",
			"shoulder_2",
			"arm_2",
			"hand_2",
			"thigh_2",
			"shank_2",
			"foot_2",
	};

	static const char *arrAttachments[] = {
			"head",
			"body",
			"shoulder",
			"arm",
			"hand",
			"thigh",
			"shank",
			"foot",
			"shoulder",
			"arm",
			"hand",
			"thigh",
			"shank",
			"foot",
	};

	creature = &(GDM->equipmentsAttribute[info.creature]);

	char bufSlot[50];
	char bufAttachment[50];
	if (info.weapon.empty()) {
		sprite->setSlotAttachment("asset_weapon", "");
		weapon = &(GDM->equipmentBlankAttribute);
	} else {
		sprintf(bufAttachment, "weapon/%s", info.weapon.c_str());
		sprite->setSlotAttachment("asset_weapon", bufAttachment);
		weapon = &(GDM->equipmentsAttribute[info.weapon]);
	}

	if (info.armor.empty()) {
		armor = &(GDM->equipmentBlankAttribute);
	} else {
		armor = &(GDM->equipmentsAttribute[info.armor]);
	}

	for (int i = 0; i < sizeof(arrAttachments) / sizeof(char *); i++) {
		sprintf(bufSlot, "asset_armor_%s", arrSlots[i]);
		if (info.armor.empty()) {
			sprite->setSlotAttachment(bufSlot, "");
		} else {
			sprintf(bufAttachment, "armor/%s_%s", info.armor.c_str(), arrAttachments[i]);
			sprite->setSlotAttachment(bufSlot, bufAttachment);
		}
	}

	if (info.shield.empty()) {
		sprite->setSlotAttachment("asset_shield", "");
		shield = &(GDM->equipmentBlankAttribute);
	} else {
		sprintf(bufAttachment, "shield/%s", info.shield.c_str());
		sprite->setSlotAttachment("asset_shield", bufAttachment);
		shield = &(GDM->equipmentsAttribute[info.shield]);
	}

	if (info.special.empty()) {
		special = &(GDM->equipmentBlankAttribute);
	} else {
		special = &(GDM->equipmentsAttribute[info.special]);
	}

	unitInfo = info;
	updateAttribute();
	updateSpritePosition();

	actions.clear();

	actions.insert(actions.begin(), weapon->actions.begin(), weapon->actions.end());
	actions.insert(actions.begin(), armor->actions.begin(), armor->actions.end());
	actions.insert(actions.begin(), shield->actions.begin(), shield->actions.end());
	actions.insert(actions.begin(), special->actions.begin(), special->actions.end());
}

void Unit::move(float ticks) {
	if (arrivedTargetPosition) {
		return;
	}

	if (!GM->floatEquals(position.x, targetPosition.x)) {
		if(static_cast<float>(fabs(position.y - targetPosition.y)) < GRID_HEIGHT) {
			float dx = speed.x * ticks;
			position.x += dx;
			if (fabs(targetPosition.x - position.x) <= fabs(dx)) {
				position.x = targetPosition.x;
			}
		}
	}

	if (!GM->floatEquals(position.y, targetPosition.y)) {
		float dy = speed.y * ticks;
		position.y += dy;
		if (fabs(targetPosition.y - position.y) <= fabs(dy)) {
			position.y = targetPosition.y;
		}
	}

	updateGridInfo();
	updateSpritePosition();
	sprite->getParent()->reorderChild(sprite, static_cast<int>(GM->screenSize.height - position.y));

	if (!_shouldUpdateGridInfo && GM->pointEquals(position, targetPosition)) {
		arrivedTargetPosition = true;
	}

}

void Unit::updateGridInfo() {
	int newLineIndex = gridInfo.lineIndex;
	int frontSquadIndex = ourArmy->frontLine / LINES_PER_SQUAD;
	if (gridInfo.squadIndex == frontSquadIndex && gridInfo.lineIndex != 0) {
		float frontLineX = ourArmy->frontLinePosition;
		int distance = static_cast<int>(fabs(position.x - frontLineX));
		int halfGridWidth = GRID_WIDTH / 2;
		int n = distance / halfGridWidth;
		newLineIndex = n / 2 + ourArmy->frontLineIndex;
		if(n % 2 > 0) {
			newLineIndex++;
		}

		if(newLineIndex >= LINES_PER_SQUAD) {
			newLineIndex = LINES_PER_SQUAD - 1;
		}
	}

	int newRowIndex = static_cast<int>((position.y + 0.5f - FORMATION_MARGIN) / GRID_HEIGHT);
	GridInfo newGridInfo;
	newGridInfo.set(gridInfo.squadIndex, newLineIndex, newRowIndex);

	if (oldGridInfo.globalGridIndex != newGridInfo.globalGridIndex) {
		bool canMoveToGrid = ourArmy->testGridForMovingUnit(this, newGridInfo.globalLineIndex, newGridInfo.rowIndex);
		if (canMoveToGrid) {
			if (ourArmy->soldiers[oldGridInfo.globalLineIndex][oldGridInfo.rowIndex] == this) {
				ourArmy->soldiers[oldGridInfo.globalLineIndex][oldGridInfo.rowIndex] = nullptr;
				ourArmy->releaseUnitOccupiedGrids(this);
			}

			gridInfo = newGridInfo;
			oldGridInfo = newGridInfo;
			ourArmy->soldiers[gridInfo.globalLineIndex][gridInfo.rowIndex] = this;
			ourArmy->updateGridsState(this, gridInfo.globalLineIndex, gridInfo.rowIndex);

			_shouldUpdateGridInfo = false;
		} else {
			_shouldUpdateGridInfo = true;
		}
	}
}

void Unit::runTo(const CCPoint &pos) {
	previousPosition = position;
	targetPosition = pos;

	transformState(UnitState::walk);
}

Unit *Unit::findDistantOpponent() {
	Unit *ret = nullptr;

	int myRange = gridInfo.globalLineIndex - ourArmy->frontLine;
	int frontLine = theirArmy->frontLine;
	for (int i = frontLine; i < TOTAL_LINES; i++) {
		if ((i - frontLine + myRange + 1) > attribute.range) {
			break;
		}

		int id = theirArmy->gridsState[i][gridInfo.rowIndex];
		Unit *enemy = theirArmy->getSoldier(id);
		if (enemy) {
			float distance = static_cast<float>(fabs(enemy->position.x - position.x));
			distance -= (MELEE_SPACE - GRID_WIDTH);
			float cases = distance / GRID_WIDTH;
			if (cases <= attribute.range) {
				ret = enemy;
			}

			break;
		}
	}

	return ret;
}

int Unit::getRangeBetweenUnits(const Unit *a, const Unit *b) {
	float distance = a->position.x - b->position.x;
	int ranges = abs(static_cast<int>(distance / GRID_WIDTH));
	return ranges;
}

void Unit::onAttack() {
	if (target && target->isAlive()) {
		Bullet *bullet = new Bullet(&attribute);
		target->addBullet(bullet);
	}

	_timeElapsed = 0;
}

void Unit::updateActions(float ticks) {

}

void Unit::handleActions(EquipmentActionTriggerType triggerType) {
	for (auto it = actions.begin(); it != actions.end();) {
		EquipmentAction &action = *it;
		bool shouldRemove = false;

		if (action.actionType == EquipmentActionType::modifier && action.timeMode == EquipmentActionTimeMode::instant) {
			if (action.targetType == EquipmentActionTargetType::wearer) {
				applyActionModifier(action, this);
			} else {
				vector<Unit *> arr = getActionTargets(action.targetType, action.value);
				for (Unit *unit : arr) {
					if (unit->isAlive()) {
						applyActionModifier(action, unit);
					}
				}
			}

			shouldRemove = true;
		}

		if (shouldRemove) {
			it = actions.erase(it);
		} else {
			++it;
		}
	}
}

void Unit::applyActionModifier(const EquipmentAction &action, Unit *target) {
	switch (action.modifyProperty) {
		case EquipmentActionModifyProperty::defense: {
			target->attribute.defense += action.value;
			if (target->attribute.defense < 0) {
				target->attribute.defense = 0;
			}
		}
		case EquipmentActionModifyProperty::reload_time: {
			target->attribute.reloadTime += action.value;
			if (target->attribute.reloadTime < 0) {
				target->attribute.reloadTime = 0;
			}
		}
		case EquipmentActionModifyProperty::casting_time: {
			target->attribute.castingTime += action.value;
			if (target->attribute.castingTime < 2) {
				target->attribute.castingTime = 2;
			}
		}
		case EquipmentActionModifyProperty::range: {
			target->attribute.range += action.value;
			if (target->attribute.range < 0) {
				target->attribute.range = 0;
			}
		}
		case EquipmentActionModifyProperty::slash_attack: {
			target->attribute.slashAttack += action.value;
			if (target->attribute.slashAttack < 0) {
				target->attribute.slashAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::blunt_attack: {
			target->attribute.bluntAttack += action.value;
			if (target->attribute.bluntAttack < 0) {
				target->attribute.bluntAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::piercing_attack: {
			target->attribute.piercingAttack += action.value;
			if (target->attribute.piercingAttack < 0) {
				target->attribute.piercingAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::explosion_attack: {
			target->attribute.explosionAttack += action.value;
			if (target->attribute.explosionAttack < 0) {
				target->attribute.explosionAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::fire_attack: {
			target->attribute.fireAttack += action.value;
			if (target->attribute.fireAttack < 0) {
				target->attribute.fireAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::ice_attack: {
			target->attribute.iceAttack += action.value;
			if (target->attribute.iceAttack < 0) {
				target->attribute.iceAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::poison_attack: {
			target->attribute.poisonAttack += action.value;
			if (target->attribute.poisonAttack < 0) {
				target->attribute.poisonAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::thunder_attack: {
			target->attribute.thunderAttack += action.value;
			if (target->attribute.thunderAttack < 0) {
				target->attribute.thunderAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::dark_attack: {
			target->attribute.darkAttack += action.value;
			if (target->attribute.darkAttack < 0) {
				target->attribute.darkAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::holy_attack: {
			target->attribute.holyAttack += action.value;
			if (target->attribute.holyAttack < 0) {
				target->attribute.holyAttack = 0;
			}
		}
		case EquipmentActionModifyProperty::slash_resist: {
			target->attribute.slashResist += action.value;
			if (target->attribute.slashResist < 0) {
				target->attribute.slashResist = 0;
			}
		}
		case EquipmentActionModifyProperty::blunt_resist: {
			target->attribute.bluntResist += action.value;
			if (target->attribute.bluntResist < 0) {
				target->attribute.bluntResist = 0;
			}
		}
		case EquipmentActionModifyProperty::piercing_resist: {
			target->attribute.piercingResist += action.value;
			if (target->attribute.piercingResist < 0) {
				target->attribute.piercingResist = 0;
			}
		}
		case EquipmentActionModifyProperty::explosion_resist: {
			target->attribute.explosionResist += action.value;
			if (target->attribute.explosionResist < 0) {
				target->attribute.explosionResist = 0;
			}
		}
		case EquipmentActionModifyProperty::fire_resist: {
			target->attribute.fireResist += action.value;
			if (target->attribute.fireResist < 0) {
				target->attribute.fireResist = 0;
			}
		}
		case EquipmentActionModifyProperty::ice_resist: {
			target->attribute.iceResist += action.value;
			if (target->attribute.iceResist < 0) {
				target->attribute.iceResist = 0;
			}
		}
		case EquipmentActionModifyProperty::poison_resist: {
			target->attribute.poisonResist += action.value;
			if (target->attribute.poisonResist < 0) {
				target->attribute.poisonResist = 0;
			}
		}
		case EquipmentActionModifyProperty::thunder_resist: {
			target->attribute.thunderResist += action.value;
			if (target->attribute.thunderResist < 0) {
				target->attribute.thunderResist = 0;
			}
		}
		case EquipmentActionModifyProperty::dark_resist: {
			target->attribute.darkResist += action.value;
			if (target->attribute.darkResist < 0) {
				target->attribute.darkResist = 0;
			}
		}
		case EquipmentActionModifyProperty::holy_resist: {
			target->attribute.holyResist += action.value;
			if (target->attribute.holyResist < 0) {
				target->attribute.holyResist = 0;
			}
		}
		default:
			break;
	}

	if (target->attribute.defense <= 0) {
		transformState(UnitState::dying);
	}
}

vector<Unit *> Unit::getActionTargets(EquipmentActionTargetType targetType, int actionValue) {
	vector<Unit *> arr;
	switch (targetType) {
		case EquipmentActionTargetType::wearer:
			arr.push_back(this);
	        break;
		case EquipmentActionTargetType::front_units:
			ourArmy->getFrontUnits(this->gridInfo, arr);
	        theirArmy->getFrontUnits(this->gridInfo, arr);
	        break;
		case EquipmentActionTargetType::melee_units:
			ourArmy->getFrontSquadUnits(arr);
	        theirArmy->getFrontSquadUnits(arr);
	        break;
		case EquipmentActionTargetType::all_units:
			ourArmy->getAllUnits(arr);
	        theirArmy->getAllUnits(arr);
	        break;
		case EquipmentActionTargetType::around_units:
			ourArmy->getAroundUnits(this->position, arr);
	        theirArmy->getAroundUnits(this->position, arr);
	        break;
		case EquipmentActionTargetType::squad_units:
			ourArmy->getSquadUnits(this->gridInfo.squadIndex, arr);
	        break;
		case EquipmentActionTargetType::our_units:
			ourArmy->getAllUnits(arr);
	        break;
		case EquipmentActionTargetType::front_line_enemy:
			theirArmy->getFrontLineUnits(arr);
	        break;
		case EquipmentActionTargetType::all_enemy:
			theirArmy->getAllUnits(arr);
		default:
			break;
	}

	return arr;
}

void Unit::onSpineEvent(const SpineEvents *event) {
	if (strutil::startsWith(event->name, "attack")) {
		static int times = 1;
		if (times % 2 == 0) {
			handleActions(EquipmentActionTriggerType::on_attack);
		}
		times++;

		onAttack();
	}
}

void Unit::startAttack() {
//	if (state == UnitState::idle || state == UnitState::aim) {
//		transformState(UnitState::attack);
//	} else if (state == UnitState::walk || state == UnitState::walk_aim) {
//		transformState(UnitState::walk_attack);
//	}
}

void Unit::moveTo(const CCPoint &pos) {
	arrivedTargetPosition = false;

	previousPosition = position;
	targetPosition = pos;

	if (!GM->floatEquals(targetPosition.x, position.x)) {
		speed.x = UNIT_SPEED;
		if (targetPosition.x < position.x) {
			speed.x = -speed.x;
		}
	}

	if (!GM->floatEquals(targetPosition.y, position.y)) {
		speed.y = 3.0f * UNIT_SPEED;
		if (targetPosition.y < position.y) {
			speed.y = -speed.y;
		}
	}

	transformState(UnitState::walk);
}

void Unit::assignTo(const CCPoint &pos, int lineIndex, int rowIndex) {
	CCPoint targetPos = pos;
	if(creature->genre == EquipmentGenre::artillery_6case) {
		if(rowIndex > gridInfo.rowIndex) {
			rowIndex--;
			targetPos.y -= GRID_HEIGHT;
		}
	} else if (creature->genre == EquipmentGenre::artillery_9case) {
		if(rowIndex > gridInfo.rowIndex) {
			rowIndex -= 2;
			targetPos.y -= GRID_HEIGHT + GRID_HEIGHT;
		}
	}

	if (!GM->pointEquals(position, targetPos) && !GM->pointEquals(targetPosition, targetPos)) {
		moveTo(pos);

		targetGridInfo.set(lineIndex * ROWS_PER_LINE + rowIndex);
		isAssigning = true;

		ourArmy->releaseUnitAssignedPosition(this);
		ourArmy->updatePositionTable(this, lineIndex, rowIndex);
	}
}
